﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Management;
using System.Threading;

using Sonic.Framework.Interfaces;

namespace Sonic.Framework.Tools
{
    public class ProcessorTool : IProcessorTool
    {
        /// <summary>
        /// How long should a process run until we deemed it timed out
        /// </summary>
        private const int TIME_OUT = 5000;

        /// <summary>
        /// 
        /// </summary>
        private readonly int MAX_NUMBER_OF_THREADS_PER_CORE = 1;
        
        /// <summary>
        /// 
        /// </summary>
        private readonly int NUMBER_OF_THREADS_TO_USE;

        /// <summary>
        /// Magic string to be replaced with an input file path at processor tool execution time
        /// </summary>
        private const string INPUT_FILEPATH = "${input}";

        /// <summary>
        /// Magic string to be replaced with an output file path at processor tool execution time
        /// </summary>
        private const string OUTPUT_FILEPATH = "${output}";

        /// <summary>
        /// The file system path to the executable for the processor tool
        /// </summary>
        public FileInfo ExecutablePath
        {
            get;
            set;
        }

        /// <summary>
        /// An alias name for this processor tool. This is how the tool is referenced from the configuration file.
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Any arguments being passed into the executable
        /// </summary>
        public string Arguments
        {
            get;
            set;
        }

        public ProcessorTool()
        {
            NUMBER_OF_THREADS_TO_USE = Environment.ProcessorCount * MAX_NUMBER_OF_THREADS_PER_CORE;
        }

        public ProcessorTool(string name)
        {
            this.Name = name;

            NUMBER_OF_THREADS_TO_USE = Environment.ProcessorCount * MAX_NUMBER_OF_THREADS_PER_CORE;
        }


        /// <summary>
        /// Builds up an arguments string for the processor tool. It simply replaces instances of the
        /// magic input and output strings with the input and output file paths.
        /// </summary>
        /// <param name="inputFilePath"></param>
        /// <param name="outputFilePath"></param>
        /// <returns></returns>
        private string BuildArgumentsString(string inputFilePath, string outputFilePath)
        {
            string args = this.Arguments;

            args = args.Replace(INPUT_FILEPATH, inputFilePath);
            args = args.Replace(OUTPUT_FILEPATH, outputFilePath);

            return args;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        private void ProcessFile(object obj)
        {
            bool traceOutput = false;

            FileInfo file = (FileInfo)obj;

            Process proc = new Process();

            try
            {
                // Setup the process
                proc.StartInfo.CreateNoWindow = true;
                proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                //proc.PriorityClass = ProcessPriorityClass.BelowNormal;
                proc.StartInfo.FileName = ExecutablePath.FullName;
                proc.StartInfo.Arguments = BuildArgumentsString(file.FullName, file.FullName);
                proc.StartInfo.UseShellExecute = false;
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.RedirectStandardError = true;

                // Log the exec
                NLogSingleton.Instance.Logger.Trace("Executing tool with command: " + proc.StartInfo.FileName + " " + proc.StartInfo.Arguments);

                proc.Start();
                proc.WaitForExit(TIME_OUT);

                // If it timed out kill the process
                if (!proc.HasExited && !proc.Responding)
                {
                    NLogSingleton.Instance.Logger.Error("PROCESS KILLED. " + file.FullName);
                    proc.Kill();
                }

                if (traceOutput)
                {
                    // Trace the standard output
                    NLogSingleton.Instance.Logger.Trace(proc.StandardOutput.ReadToEnd());

                    // Trace any errors
                    string errors = proc.StandardError.ReadToEnd();
                    if (errors != null && errors != "")
                    {
                        NLogSingleton.Instance.Logger.Error(errors);
                    }
                }
            }
            finally
            {
                // Ensure the process is closed
                proc.Close();
            }
        }

        /// <summary>
        /// Runs the processor tool over all files in the passed List of FileInfo instances
        /// </summary>
        /// <param name="directoryFiles"></param>
        public void Run(List<FileInfo> directoryFiles)
        {
            ThreadPool.SetMaxThreads(NUMBER_OF_THREADS_TO_USE, NUMBER_OF_THREADS_TO_USE);

            foreach (FileInfo file in directoryFiles)
            {
                // Null is a special case, don't do anything
                if (this.Name == "Null")
                {
                    NLogSingleton.Instance.Logger.Trace("Null processor tool does nothing");

                    return; // Can't do anything with null
                }

                ThreadPool.QueueUserWorkItem(ProcessFile, file);
                //ProcessFile(file);
            }
        }
    }
}
